

#include <fstream>
#include <sstream>

#include <boost/cerrno.hpp>
#include <boost/filesystem.hpp>

using boost::system::error_code;
using boost::system::system_category;
using boost::system::system_error;


#include "aucab.hpp"


namespace   confab   {


#define	N_CHECKPOINT_INTERVAL	1000000


Aucab::Aucab( const std::string env_home, const std::string db_home, const std::string data_root, u_int32_t n_tables )
        : _env_home( env_home ),
		  _db_home( db_home ),
		  _data_root( data_root ),
		  _n_tables( n_tables )
{
	initialize();

	_count_put_or_erase = 0;
}

Aucab::~Aucab()
{
	finalize();
}


///////////////////////////////////////////////////////////////////////
//

void
Aucab::initialize()
{
	// initialize env home
	boost::filesystem::path	envhome_path( _env_home );
	if ( !boost::filesystem::exists( envhome_path ) ) {
		if ( !boost::filesystem::create_directories( envhome_path ) ) {	
            std::ostringstream  msg;
            msg << "fail to create env-home directory '" << _env_home << "'";
			throw boost::filesystem::filesystem_error( msg.str(), envhome_path, error_code(errno, system_category) );
		}
	}

	boost::filesystem::path	dbconfig_path = envhome_path / "DB_CONFIG";
	if ( !boost::filesystem::exists( dbconfig_path ) ) {
		// create default DB_CONFIG
		createDefaultDbConfig( dbconfig_path.string() );
	}

	// initialize db home
	boost::filesystem::path	dbhome_path( _db_home );
	if ( !boost::filesystem::exists( dbhome_path ) ) {
		if ( !boost::filesystem::create_directories( dbhome_path ) ) {	
			throw boost::filesystem::filesystem_error( "fail to create db-home directory", dbhome_path, error_code(errno, system_category) );
		}
	}

	// initialize data root
	boost::filesystem::path	root_path( _data_root );
	// check directory
	if ( !boost::filesystem::exists( root_path ) ) {
		////std::ostringstream	msg;
		////msg << "Data root dir '" << root_path.string() << "' does NOT exist";
		////throw std::runtime_error( msg.str() );
		if ( !boost::filesystem::create_directories( root_path ) ) {	
			throw boost::filesystem::filesystem_error( "fail to create data-home directory", root_path, error_code(errno, system_category) );
		}
	}
	if ( !boost::filesystem::is_directory( root_path ) ) {
		std::ostringstream	msg;
		msg << "Data root dir '" << root_path.string() << "' is NOT a directory";
		throw std::runtime_error( msg.str() );
	}

	dbenv_p = NULL;
	recoverDbEnv( envhome_path.string() );
	openDbEnv( envhome_path.string() );
}

void
Aucab::finalize()
{
	// close all bins
	bins_iterator_type	it = _bins.begin();
	while ( it != _bins.end() ) {
		aubin_ptr&	bin_p = it->second;
		delete bin_p;

		++it;
	}

	if (_count_put_or_erase > 0) {
		dbenv_p->txn_checkpoint( 0, 0, 0 );
		_count_put_or_erase = 0;
	}   

	// close db environment
	closeDbEnv();
}

void
Aucab::createDefaultDbConfig( const std::string filename )
{
	std::ofstream	ofs;

	try {
		ofs.open( filename.c_str() );

		ofs << std::endl
			<< std::endl << "set_cachesize 0 524288000 0"
			<< std::endl << ""
			<< std::endl << "set_lg_regionmax  4194304"
			<< std::endl << "set_lg_max        10485760"
			<< std::endl << "set_lg_bsize      2097152"
			<< std::endl << "#set_lg_dir        /path/to/var/confab/dblog"
			<< std::endl << ""
			<< std::endl << "set_lk_max_objects  65536"
			<< std::endl << "set_lk_max_locks    131072"
			<< std::endl << "set_lk_max_lockers  131072"
			<< std::endl << ""
			<< std::endl << "set_flags         DB_TXN_WRITE_NOSYNC "
			<< std::endl;

		ofs.close();
	}
	catch( std::exception& e) {
		if (ofs.is_open()) {
			ofs.close();
		}

		std::ostringstream	msg;
		msg << "fail to create default config " << filename;
		throw std::runtime_error( msg.str() );
	}
}


///////////////////////////////////////////////////////////////////////
//


int 
Aucab::insert( cid_type   cid,
         const std::string& kpin,
         const std::string& key,
         const std::string& data )
{
    aubin_ptr   aubin_p = getBin( cid );

	++_count_put_or_erase;
	if (_count_put_or_erase > N_CHECKPOINT_INTERVAL) {
		dbenv_p->txn_checkpoint( 0, 0, 0 );
		_count_put_or_erase = 0;
	}   

    return  aubin_p->insert( kpin, key, data );
}

int 
Aucab::insertIgnore( cid_type   cid,
         const std::string& kpin,
         const std::string& key,
         const std::string& data )
{
    aubin_ptr   aubin_p = getBin( cid );

	++_count_put_or_erase;
	if (_count_put_or_erase > N_CHECKPOINT_INTERVAL) {
		dbenv_p->txn_checkpoint( 0, 0, 0 );
		_count_put_or_erase = 0;
	}   

    return  aubin_p->insertIgnore( kpin, key, data );
}

int 
Aucab::erase( cid_type cid,
           const std::string& kpin,
           const std::string& key )
{
    aubin_ptr   aubin_p = getBin( cid );

	++_count_put_or_erase;
	if (_count_put_or_erase > N_CHECKPOINT_INTERVAL) {
		dbenv_p->txn_checkpoint( 0, 0, 0 );
		_count_put_or_erase = 0;
	}   

    return  aubin_p->erase( kpin, key );
}

int 
Aucab::find( cid_type   cid,
         const std::string& kpin,
         const std::string& key,
               std::string& data )
{
    aubin_ptr   aubin_p = getBin( cid );

    return  aubin_p->find( kpin, key, data );
}



///////////////////////////////////////////////////////////////////////
//


Aucab::aubin_ptr
Aucab::getBin( cid_type cid )
{
    bins_iterator_type  it = _bins.find( cid );
    if ( _bins.end() != it ) {
        return it->second;
    }

    std::string data_root = getDataRoot( cid );

    // if data-root does NOT exist, try to make it
	boost::filesystem::path	data_path( data_root );
	if ( !boost::filesystem::exists( data_path ) ) {
		if ( !boost::filesystem::create_directory( data_path ) ) {	
			throw boost::filesystem::filesystem_error( "fail to create bin-data directory", data_path, error_code(errno, system_category) );
		}
	}

	std::string	index_file = getDataIndexFile( cid );

    aubin_ptr   aubin_p = new Aubin( data_root, index_file, dbenv_p, _n_tables );

    _bins.insert( std::make_pair( cid, aubin_p ) );
    return aubin_p;
}


std::string
Aucab::getDataRoot( cid_type cid )
{
    std::ostringstream   path;

	if ( cid < 0 ) {
		path << _data_root << "/" << "default";
	}
	else {
		path << _data_root << "/" << cid;
	}

    return path.str();
}

std::string
Aucab::getDataIndexFile( cid_type cid )
{
    std::ostringstream   index;

	if ( cid < 0 ) {
		index << _db_home << "/" << "ab" << "default" << ".db";
	}
	else {
		index << _db_home << "/" << "ab" << cid << ".db";
	}

    return index.str();
}


///////////////////////////////////////////////////////////////////////
//


void
Aucab::openDbEnv( const std::string envhome )
{
	if (NULL == dbenv_p) {
		dbenv_p = new DbEnv(0);

		u_int32_t	env_flags = DB_CREATE     | // If the environment does not exist, create it.
								DB_INIT_LOCK  | // Initialize locking
								DB_INIT_LOG   | // Initialize logging
								DB_INIT_MPOOL | // Initialize the cache
								DB_INIT_TXN   | // Initialize transactions
								DB_PRIVATE    |
								DB_THREAD;

		try {
			dbenv_p->open( envhome.c_str(), env_flags, 0664 );

			// Turn off system buffering of Berkeley DB log files to avoid double caching.
			try {
				dbenv_p->log_set_config( DB_LOG_DIRECT, 1 );
			}
			catch( ... ) {
			}

			// Berkeley DB will automatically remove log files that are no longer needed.
			dbenv_p->log_set_config( DB_LOG_AUTO_REMOVE, 1 );

			dbenv_p->log_set_config( DB_LOG_ZERO, 1 );
		}
		catch(DbException& e) {
			delete dbenv_p;
			dbenv_p = NULL;

			std::ostringstream	msg;
			msg << "fail to open DbEnv " << envhome << " -- " << e.what();
			throw std::runtime_error( msg.str() );
		}
	}
}


void
Aucab::closeDbEnv(void)
{
	if (NULL != dbenv_p) {
		try {
			dbenv_p->close( 0 );
		}
		catch(DbException& e) {
			std::ostringstream	msg;
			msg << "fail to close DbEnv " << " -- " << e.what();
			throw std::runtime_error( msg.str() );
		}

		delete dbenv_p;
		dbenv_p = NULL;
	}
}


void
Aucab::recoverDbEnv( const std::string envhome )
{
	if (NULL == dbenv_p) {
		dbenv_p = new DbEnv(0);

		u_int32_t	env_flags = DB_CREATE     | // If the environment does not exist, create it.
								DB_INIT_LOCK  | // Initialize locking
								DB_INIT_LOG   | // Initialize logging
								DB_INIT_MPOOL | // Initialize the cache
								DB_INIT_TXN   | // Initialize transactions
								DB_PRIVATE    |
								//DB_FAILCHK    |
								//DB_REGISTER   |
								//DB_RECOVER;
								DB_RECOVER_FATAL;

		try {
			dbenv_p->open( envhome.c_str(), env_flags, 0664 );

			// Turn off system buffering of Berkeley DB log files to avoid double caching.
			////dbenv_p->log_set_config( DB_LOG_DIRECT, 1 );

			// Berkeley DB will automatically remove log files that are no longer needed.
			dbenv_p->log_set_config( DB_LOG_AUTO_REMOVE, 1 );

			dbenv_p->log_set_config( DB_LOG_ZERO, 1 );
		}
		catch(DbException& e) {
			delete dbenv_p;
			dbenv_p = NULL;

			std::ostringstream	msg;
			msg << "fail to open DbEnv " << envhome << " for recovery -- " << e.what();
			throw std::runtime_error( msg.str() );
		}

		try {
			dbenv_p->close( 0 );
		}
		catch(DbException& e) {
			std::ostringstream	msg;
			msg << "fail to close DbEnv " << envhome << " for recovery -- " << e.what();
			throw std::runtime_error( msg.str() );
		}

		delete dbenv_p;
		dbenv_p = NULL;
	}
}



}   //// namespace confab


